﻿using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
using JetBrains.Annotations;
using Dyn.News.Models;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Routable.Models;
using Orchard.Core.Routable.Services;
using Orchard.Core.XmlRpc;
using Orchard.Core.XmlRpc.Models;
using Orchard.Environment.Extensions;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Mvc.Extensions;
using Orchard.Security;
using Dyn.News.Extensions;
using Orchard.Mvc.Html;
using Orchard;

namespace Dyn.News.Services {
    [UsedImplicitly]
    [OrchardFeature("Dyn.News.RemotePublishing")]
    public class XmlRpcHandler : IXmlRpcHandler {
        private readonly ICategoryervice _Categoryervice;
        private readonly IArticleService _articleService;
        private readonly IContentManager _contentManager;
        private readonly IAuthorizationService _authorizationService;
        private readonly IMembershipService _membershipService;
        private readonly IRoutableService _routableService;
        private readonly RouteCollection _routeCollection;

        public XmlRpcHandler(ICategoryervice Categoryervice, IArticleService articleService, IContentManager contentManager,
            IAuthorizationService authorizationService, IMembershipService membershipService, IRoutableService routableService,
            RouteCollection routeCollection) {
            _Categoryervice = Categoryervice;
            _articleService = articleService;
            _contentManager = contentManager;
            _authorizationService = authorizationService;
            _membershipService = membershipService;
            _routableService = routableService;
            _routeCollection = routeCollection;
            Logger = NullLogger.Instance;
            T = NullLocalizer.Instance;
        }

        public ILogger Logger { get; set; }
        public Localizer T { get; set; }

        public void SetCapabilities(XElement options) {
            const string manifestUri = "http://schemas.microsoft.com/wlw/manifest/weCategory";
            options.SetElementValue(XName.Get("supportsSlug", manifestUri), "Yes");
        }

        public void Process(XmlRpcContext context) {
            var urlHelper = new UrlHelper(context.ControllerContext.RequestContext, _routeCollection);

            if (context.Request.MethodName == "Categoryger.getUsersCategory") {
                var result = MetaWeCategoryGetUserCategory(urlHelper,
                    Convert.ToString(context.Request.Params[1].Value),
                    Convert.ToString(context.Request.Params[2].Value));

                context.Response = new XRpcMethodResponse().Add(result);
            }

            if (context.Request.MethodName == "metaWeCategory.getRecentPosts") {
                var result = MetaWeCategoryGetRecentPosts(urlHelper,
                    Convert.ToString(context.Request.Params[0].Value),
                    Convert.ToString(context.Request.Params[1].Value),
                    Convert.ToString(context.Request.Params[2].Value),
                    Convert.ToInt32(context.Request.Params[3].Value),
                    context._drivers);

                context.Response = new XRpcMethodResponse().Add(result);
            }

            if (context.Request.MethodName == "metaWeCategory.newPost") {
                var result = MetaWeCategoryNewPost(
                    Convert.ToString(context.Request.Params[0].Value),
                    Convert.ToString(context.Request.Params[1].Value),
                    Convert.ToString(context.Request.Params[2].Value),
                    (XRpcStruct)context.Request.Params[3].Value,
                    Convert.ToBoolean(context.Request.Params[4].Value),
                    context._drivers);

                context.Response = new XRpcMethodResponse().Add(result);
            }

            if (context.Request.MethodName == "metaWeCategory.getPost") {
                var result = MetaWeCategoryGetPost(
                    urlHelper,
                    Convert.ToInt32(context.Request.Params[0].Value),
                    Convert.ToString(context.Request.Params[1].Value),
                    Convert.ToString(context.Request.Params[2].Value),
                    context._drivers);
                context.Response = new XRpcMethodResponse().Add(result);
            }

            if (context.Request.MethodName == "metaWeCategory.editPost") {
                var result = MetaWeCategoryEditPost(
                    Convert.ToInt32(context.Request.Params[0].Value),
                    Convert.ToString(context.Request.Params[1].Value),
                    Convert.ToString(context.Request.Params[2].Value),
                    (XRpcStruct)context.Request.Params[3].Value,
                    Convert.ToBoolean(context.Request.Params[4].Value),
                    context._drivers);
                context.Response = new XRpcMethodResponse().Add(result);
            }

            if (context.Request.MethodName == "Categoryger.deletePost") {
                var result = MetaWeCategoryDeletePost(
                    Convert.ToString(context.Request.Params[1].Value),
                    Convert.ToString(context.Request.Params[2].Value),
                    Convert.ToString(context.Request.Params[3].Value),
                    context._drivers);
                context.Response = new XRpcMethodResponse().Add(result);
            }
        }

        private XRpcArray MetaWeCategoryGetUserCategory(UrlHelper urlHelper,
            string userName,
            string password) {

            IUser user = ValidateUser(userName, password);

            XRpcArray array = new XRpcArray();
            foreach (NewsCategoryPart category in _Categoryervice.Get()) {
                // User needs to at least have permission to edit its own category posts to access the service
                if (_authorizationService.TryCheckAccess(Permissions.EditArticle, user, category)) {

                    NewsCategoryPart categoryPart = category;
                    array.Add(new XRpcStruct()
                                  .Set("url", urlHelper.AbsoluteAction(() => urlHelper.Category(categoryPart)))
                                  .Set("categoryid", category.Id)
                                  .Set("categoryName", category.Name));
                }
            }

            return array;
        }

        private XRpcArray MetaWeCategoryGetRecentPosts(
            UrlHelper urlHelper,
            string categoryID,
            string userName,
            string password,
            int numberOfPosts,
            IEnumerable<IXmlRpcDriver> drivers) {

            IUser user = ValidateUser(userName, password);

            // User needs to at least have permission to edit its own category posts to access the service
            _authorizationService.CheckAccess(Permissions.EditArticle, user, null);

            NewsCategoryPart Category = _contentManager.Get<NewsCategoryPart>(Convert.ToInt32(categoryID));
            if (Category == null) {
                throw new ArgumentException();
            }

            var array = new XRpcArray();
            foreach (var Article in _articleService.Get(Category, 0, numberOfPosts, VersionOptions.Latest)) {
                var postStruct = CreateCategorytruct(Article, urlHelper);

                foreach (var driver in drivers)
                    driver.Process(postStruct);

                array.Add(postStruct);
            }
            return array;
        }

        private int MetaWeCategoryNewPost(
            string categoryID,
            string userName,
            string password,
            XRpcStruct content,
            bool publish,
            IEnumerable<IXmlRpcDriver> drivers) {

            IUser user = ValidateUser(userName, password);

            // User needs permission to edit or publish its own category posts
            _authorizationService.CheckAccess(publish ? Permissions.PublishArticle : Permissions.EditArticle, user, null);

            NewsCategoryPart Category = _contentManager.Get<NewsCategoryPart>(Convert.ToInt32(categoryID));
            if (Category == null)
                throw new ArgumentException();

            var title = content.Optional<string>("title");
            var description = content.Optional<string>("description");
            var slug = content.Optional<string>("wp_slug");

            var Article = _contentManager.New<ArticlePart>("Article");

            // BodyPart
            if (Article.Is<BodyPart>()) {
                Article.As<BodyPart>().Text = description;
            }

            //CommonPart
            if (Article.Is<ICommonPart>()) {
                Article.As<ICommonPart>().Owner = user;
                Article.As<ICommonPart>().Container = Category;
            }

            //RoutePart
            if (Article.Is<RoutePart>()) {
                Article.As<RoutePart>().Title = title;
                Article.As<RoutePart>().Slug = slug;
                _routableService.FillSlugFromTitle(Article.As<RoutePart>());
                Article.As<RoutePart>().Path = Article.As<RoutePart>().GetPathWithSlug(Article.As<RoutePart>().Slug);
            }

            _contentManager.Create(Article, VersionOptions.Draft);

            var publishedUtc = content.Optional<DateTime?>("dateCreated");
            if (publish && (publishedUtc == null || publishedUtc <= DateTime.UtcNow))
                _articleService.Publish(Article);

            foreach (var driver in drivers)
                driver.Process(Article.Id);

            return Article.Id;
        }

        private XRpcStruct MetaWeCategoryGetPost(
            UrlHelper urlHelper,
            int articleId,
            string userName,
            string password,
            IEnumerable<IXmlRpcDriver> drivers) {

            IUser user = ValidateUser(userName, password);
            var Article = _articleService.Get(articleId, VersionOptions.Latest);
            if (Article == null)
                throw new ArgumentException();

            _authorizationService.CheckAccess(Permissions.EditArticle, user, Article);

            var postStruct = CreateCategorytruct(Article, urlHelper);

            foreach (var driver in drivers)
                driver.Process(postStruct);

            return postStruct;
        }

        private bool MetaWeCategoryEditPost(
            int articleId,
            string userName,
            string password,
            XRpcStruct content,
            bool publish,
            IEnumerable<IXmlRpcDriver> drivers) {

            IUser user = ValidateUser(userName, password);
            var Article = _articleService.Get(articleId, VersionOptions.DraftRequired);
            if (Article == null)
                throw new ArgumentException();

            _authorizationService.CheckAccess(publish ? Permissions.PublishArticle : Permissions.EditArticle, user, Article);

            var title = content.Optional<string>("title");
            var description = content.Optional<string>("description");
            var slug = content.Optional<string>("wp_slug");

            // BodyPart
            if (Article.Is<BodyPart>()) {
                Article.As<BodyPart>().Text = description;
            }

            //RoutePart
            if (Article.Is<RoutePart>()) {
                Article.As<RoutePart>().Title = title;
                Article.As<RoutePart>().Slug = slug;
                _routableService.FillSlugFromTitle(Article.As<RoutePart>());
                Article.As<RoutePart>().Path = Article.As<RoutePart>().GetPathWithSlug(Article.As<RoutePart>().Slug);
            }

            var publishedUtc = content.Optional<DateTime?>("dateCreated");
            if (publish && (publishedUtc == null || publishedUtc <= DateTime.UtcNow))
                _articleService.Publish(Article);

            foreach (var driver in drivers)
                driver.Process(Article.Id);

            return true;
        }

        private bool MetaWeCategoryDeletePost(
            string articleId,
            string userName,
            string password,
            IEnumerable<IXmlRpcDriver> drivers) {

            IUser user = ValidateUser(userName, password);
            var Article = _articleService.Get(Convert.ToInt32(articleId), VersionOptions.Latest);
            if (Article == null)
                throw new ArgumentException();

            _authorizationService.CheckAccess(Permissions.DeleteArticle, user, Article);

            foreach (var driver in drivers)
                driver.Process(Article.Id);

            _articleService.Delete(Article);
            return true;
        }

        private IUser ValidateUser(string userName, string password) {
            IUser user = _membershipService.ValidateUser(userName, password);
            if (user == null) {
                throw new OrchardCoreException(T("The username or e-mail or password provided is incorrect."));
            }

            return user;
        }

        private static XRpcStruct CreateCategorytruct(
            ArticlePart ArticlePart,
            UrlHelper urlHelper) {

            var url = urlHelper.AbsoluteAction(() => urlHelper.ItemDisplayUrl(ArticlePart));

            if (ArticlePart.HasDraft()) {
                url = urlHelper.AbsoluteAction("Preview", "Item", new { area = "Contents", id = ArticlePart.ContentItem.Id });
            }

            var Categorytruct = new XRpcStruct()
                .Set("articleId", ArticlePart.Id)
                .Set("title", ArticlePart.Title)
                .Set("wp_slug", ArticlePart.Slug)
                .Set("description", ArticlePart.Text)
                .Set("link", url)
                .Set("permaLink", url);

            if (ArticlePart.PublishedUtc != null) {
                Categorytruct.Set("dateCreated", ArticlePart.PublishedUtc);
                Categorytruct.Set("date_created_gmt", ArticlePart.PublishedUtc);
            }

            return Categorytruct;
        }
    }
}
